﻿//-----------------------------------------------------------------------
// <copyright file="GlassProgressBar.cs" company="Redmist">
//     Copyright (c) Damien T Smith. All rights reserved.
// </copyright>
// <author>Damien Smith</author>
//-----------------------------------------------------------------------

namespace Redmist.UsageMonitor.CustomControls
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Imaging;
    using System.Drawing.Text;
    using System.Windows.Forms;

    /// <summary>The colour of a progress bar indicating its status.</summary>
    public enum ProgressStatusColor
    {
        /// <summary>Represents a green progress bar.</summary>
        Green,

        /// <summary>Represents a yellow progress bar.</summary>
        Yellow,

        /// <summary>Represents a red progress bar.</summary>
        Red
    }

    /// <summary>A custom progress bar user control with percentage text, adjustable colors and a glass effect.</summary>
    public partial class GlassProgressBar : UserControl
    {
        /// <summary>The ARGB color value for the bottom of a red progress bar.</summary>
        private const int BarBottomRedArgb = -7602176;

        /// <summary>The ARGB color value for the top of a red progress bar.</summary>
        private const int BarTopRedArgb = -65536;

        /// <summary>The ARGB color value for the bottom of a yellow progress bar.</summary>
        private const int BarBottomYellowArgb = -10198016;

        /// <summary>The ARGB color value for the top of a yellow progress bar.</summary>
        private const int BarTopYellowArgb = -256;

        /// <summary>The ARGB color value for the bottom of a green progress bar.</summary>
        private const int BarBottomGreenArgb = -16751616;

        /// <summary>The ARGB color value for the top of a green progress bar.</summary>
        private const int BarTopGreenArgb = -16718336;

        /// <summary>The ARGB color value for the bottom of the highlight 'glass' effect.</summary>
        private const int HighlightBottomArgb = 1691538130;

        /// <summary>The ARGB color value for the top of the highlight 'glass' effect.</summary>
        private const int HighlightTopArgb = -754974721;

        /// <summary>The height of the percent text in relation to the height of the control.</summary>
        private const float FontHeightRatio = 0.7f;

        /// <summary>The minimum value for the progress bar.</summary>
        private int minimumValue;

        /// <summary>The maximum value for the progress bar.</summary>
        private int maximumValue;

        /// <summary>The current value for the progress bar.</summary>
        private int progressValue;

        /// <summary>A System.Drawing.Color representing the current bottom color of the progress bar.</summary>
        private Color barBottomColor;

        /// <summary>A System.Drawing.Color representing the current bottom color of the progress bar.</summary>
        private Color barTopColor;

        /// <summary>A System.Drawing.Color representing the current color of the percent text shadow.</summary>
        private Color textShadowColor;

        /// <summary>A Redmist.UsageMonitor.CustomControls.ProgressStatusColor representing the current status color of the progress bar.</summary>
        private ProgressStatusColor statusColor;

        /// <summary>A lookup for progress bar color values.</summary>
        private Dictionary<ProgressStatusColor, int[]> colorLookup;

        /// <summary>Initializes a new instance of the GlassProgressBar class.</summary>
        public GlassProgressBar()
        {
            this.InitializeComponent();
            this.colorLookup = new Dictionary<ProgressStatusColor, int[]>();
            this.InitializeProgressBarColors();
            this.minimumValue = 0;
            this.maximumValue = 100;
            this.progressValue = 50;
            this.StatusColor = ProgressStatusColor.Green;
            this.textShadowColor = Color.Black;
            this.BackColor = Color.Gray;
            this.ForeColor = Color.White;
            this.Name = "SmoothProgressBar";
            this.Size = new Size(170, 17);
        }

        /// <summary>Gets or sets the color of the progress bar.</summary>
        [Description("The Colour of the Progress Bar"), Category("Appearance"), Browsable(true)]
        public ProgressStatusColor StatusColor
        {
            get
            {
                return this.statusColor;
            }

            set
            {
                int[] colorPair;
                this.colorLookup.TryGetValue(value, out colorPair);
                this.barTopColor = Color.FromArgb(colorPair[0]);
                this.barBottomColor = Color.FromArgb(colorPair[1]);
                this.statusColor = value;
                this.Invalidate();
            }
        }

        /// <summary>Gets or sets the forecolor of the progress bar text.</summary>
        [Description("The Percent Text ForeColor"), Category("Appearance"), Browsable(true)]
        public override Color ForeColor
        {
            get
            {
                return base.ForeColor;
            }

            set
            {
                base.ForeColor = value;
                this.Invalidate();
            }
        }

        /// <summary>Overrides the Font property to disable setting of the control font.</summary>
        [Browsable(false)]
        public override Font Font
        {
            get
            {
                return base.Font;
            }
        }

        /// <summary>Gets or sets the minimum value for the progress bar.</summary>
        public int Minimum
        {
            get
            {
                return this.minimumValue;
            }

            set
            {
                if (value < 0)
                {
                    this.minimumValue = 0;
                }

                if (value > this.maximumValue)
                {
                    this.minimumValue = value;
                    this.maximumValue = value;
                }

                if (this.progressValue < this.minimumValue)
                {
                    this.progressValue = this.minimumValue;
                }

                this.Invalidate();
            }
        }

        /// <summary>Gets or sets the maximum value for the progress bar.</summary>
        public int Maximum
        {
            get
            {
                return this.maximumValue;
            }

            set
            {
                if (value < this.minimumValue)
                {
                    this.minimumValue = value;
                }

                this.maximumValue = value;
                if (this.progressValue > this.maximumValue)
                {
                    this.progressValue = this.maximumValue;
                }

                this.Invalidate();
            }
        }

        /// <summary>Gets or sets the value for the progress bar.</summary>
        public int Value
        {
            get
            {
                return this.progressValue;
            }

            set
            {
                if (value == int.MaxValue)
                {
                    this.progressValue = int.MaxValue;
                    this.Invalidate();
                    return;
                }

                if (value < this.minimumValue)
                {
                    this.progressValue = this.minimumValue;
                }
                else if (value > this.maximumValue)
                {
                    this.progressValue = this.maximumValue;
                }
                else
                {
                    this.progressValue = value;
                }

                int progress = this.progressValue - this.minimumValue;
                int range = this.maximumValue - this.minimumValue;
                this.Percentage = (float)progress / range * 100;
                this.Invalidate();
            }
        }

        /// <summary>Gets the current value of the progress bar as a percentage.</summary>
        public float Percentage { get; private set; }

        /// <summary>Sets the size of the main PictureBox to the same size as the client control.</summary>
        /// <param name="e">A System.EventArgs representing the event arguments.</param>
        protected override void OnResize(EventArgs e)
        {
            this.pictureBox.Size = new Size(this.ClientRectangle.Width, this.ClientRectangle.Height);
            this.Invalidate();
        }

        /// <summary>Redraws the control when the OnPaint event is fired.</summary>
        /// <param name="e">A System.Windows.Forms.PaintEventArgs representing the event arguments.</param>
        protected override void OnPaint(PaintEventArgs e)
        {
            Bitmap bitmap = new Bitmap(this.pictureBox.Width, this.pictureBox.Height, PixelFormat.Format24bppRgb);
            using (Graphics graphics = Graphics.FromImage(bitmap))
            {
                this.DrawBackground(graphics);
                this.DrawProgress(graphics);
                this.DrawHighlight(graphics);
                this.Draw3DBorder(graphics);
                this.DrawPercentText(graphics);
            }

            this.pictureBox.Image = bitmap;
        }

        /// <summary>Initializes the lookup for progress bar status colors.</summary>
        private void InitializeProgressBarColors()
        {
            this.colorLookup.Add(ProgressStatusColor.Green, new int[2] { BarTopGreenArgb, BarBottomGreenArgb });
            this.colorLookup.Add(ProgressStatusColor.Yellow, new int[2] { BarTopYellowArgb, BarBottomYellowArgb });
            this.colorLookup.Add(ProgressStatusColor.Red, new int[2] { BarTopRedArgb, BarBottomRedArgb });
        }

        /// <summary>Draws a rectangular border around the client.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void Draw3DBorder(Graphics graphics)
        {
            int penWidth = (int)Pens.White.Width;
            graphics.DrawLine(Pens.DarkGray, new Point(this.ClientRectangle.Left, this.ClientRectangle.Top), new Point(this.ClientRectangle.Width - penWidth, this.ClientRectangle.Top));
            graphics.DrawLine(Pens.DarkGray, new Point(this.ClientRectangle.Left, this.ClientRectangle.Top), new Point(this.ClientRectangle.Left, this.ClientRectangle.Height - penWidth));
            graphics.DrawLine(Pens.White, new Point(this.ClientRectangle.Left, this.ClientRectangle.Height - penWidth), new Point(this.ClientRectangle.Width - penWidth, this.ClientRectangle.Height - penWidth));
            graphics.DrawLine(Pens.White, new Point(this.ClientRectangle.Width - penWidth, this.ClientRectangle.Top), new Point(this.ClientRectangle.Width - penWidth, this.ClientRectangle.Height - penWidth));
        }

        /// <summary>Draws a transparent highlight over the control to give a 'glass' effect.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void DrawHighlight(Graphics graphics)
        {
            Rectangle highlightRectangle = this.ClientRectangle;
            highlightRectangle.Height = this.Height / 3;
            float startX = this.Width / 2f;
            float startY = highlightRectangle.Height;
            float endX = startX;
            float endY = 0f;
            using (LinearGradientBrush highlightBrush = new LinearGradientBrush(new PointF(startX, startY), new PointF(endX, endY), Color.FromArgb(HighlightBottomArgb), Color.FromArgb(HighlightTopArgb)))
            {
                graphics.FillRectangle(highlightBrush, highlightRectangle);
            }
        }

        /// <summary>Draws the progress amount as a colored gradient using the current status colors.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void DrawProgress(Graphics graphics)
        {
            float startX;
            if (this.progressValue == int.MaxValue)
            {
                startX = this.ClientRectangle.Width;
            }
            else
            {
                startX = this.ClientRectangle.Width * this.Percentage / 100;
            }

            float startY = this.ClientRectangle.Height;
            float endX = startX;
            float endY = 0f;
            Rectangle progressRectangle = this.ClientRectangle;
            progressRectangle.Width = (int)startX;
            using (LinearGradientBrush progressBrush = new LinearGradientBrush(new PointF(startX, startY), new PointF(endX, endY), this.barBottomColor, this.barTopColor))
            {
                graphics.FillRectangle(progressBrush, progressRectangle);
            }
        }

        /// <summary>Draws the background for the control using a solid fill.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void DrawBackground(Graphics graphics)
        {
            using (Brush backBrush = new SolidBrush(this.BackColor))
            {
                graphics.FillRectangle(backBrush, this.ClientRectangle);
            }
        }

        /// <summary>Draws the progress percentage as text with a shadow on top of the progress bar.</summary>
        /// <param name="graphics">A System.Drawing.Graphics representing the GDI+ drawing surface.</param>
        private void DrawPercentText(Graphics graphics)
        {
            Font percentFont = new Font(this.Parent.Font.FontFamily, this.Height * FontHeightRatio, FontStyle.Bold, GraphicsUnit.Pixel);
            string percentString;
            if (this.Value == int.MaxValue)
            {
                percentString = "∞";
            }
            else
            {
                percentString = this.Percentage.ToString("0.0") + "%";
            }

            SizeF size = graphics.MeasureString(percentString, percentFont, this.Size);
            int textX = (int)size.Width;
            int textY = (int)size.Height;
            int left = (this.Width / 2) - (textX / 2);
            int top = (this.Height / 2) - (textY / 2);
            graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
            using (Brush backBrush = new SolidBrush(this.textShadowColor))
            {
                graphics.DrawString(percentString, percentFont, backBrush, left + 1, top);
            }

            using (Brush foreBrush = new SolidBrush(this.ForeColor))
            {
                graphics.DrawString(percentString, percentFont, foreBrush, left, top - 1);
            }
        }
    }
}
